//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.UIDesigner
{
    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using Microsoft.Research.ScientificWorkflow.TridentModel;
    using System.Collections.ObjectModel;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities;
    using System.Windows.Input;
    using System.Globalization;
    using System.Windows.Media;

    /// <summary>
    /// Interaction logic for SubsectionExpandedCtrl.xaml
    /// </summary>
    public partial class SubsectionExpandedCtrl : ITridentUIElement, ISubsectionControl
    {
        /// <summary>
        /// The offset between the ChildElements collection and the MainContentHolder's children collection.
        /// This is due to the insertion of start element.
        /// </summary>
        private readonly int MetaUiElementsOffset = 1;

        /// <summary>
        /// The subsectionmodel represented.
        /// </summary>
        private TridentSubsectionModel subsectionModel;

        /// <summary>
        /// The composer control containing this control.
        /// </summary>
        private ComposerControl composerControl;
        
        /// <summary>
        /// The children of this composite element.
        /// </summary>
        private ObservableCollection<ITridentUIElement> childElements = new ObservableCollection<ITridentUIElement>();
        
        /// <summary>
        /// Flag indicating if the collapsed ctrl load event has been registered or not.
        /// </summary>
        private bool collapsedLoadInitialized;

        /// <summary>
        /// Context menu items which are to be shown only for subsections.
        /// </summary>
        private Collection<MenuItem> subsectionSpecificItems = new Collection<MenuItem>();

        #region Public Properties

        /// <summary>
        /// Gets the subsection model.
        /// </summary>
        public TridentSubsectionModel ContainedSubsectionModel
        {
            get
            {
                return this.subsectionModel;
            }
        }

        /// <summary>
        /// The collapsed control.
        /// </summary>
        public SubsectionCollapsedCtrl CollapsedCtrl
        {
            get;
            set;
        }

        #endregion Public Properties

        #region Events

        /// <summary>
        /// Fired when the user wants to remove the subsection only, keeping it's contents intact.
        /// </summary>
        public event EventHandler<RemoveSubsectionEventArgs> RemoveSubsection;

        #endregion Events

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="subsectionModel">
        /// The subsection this control represents.
        /// </param>
        public SubsectionExpandedCtrl(TridentSubsectionModel subsectionModel)
            : this()
        {
            this.subsectionModel = subsectionModel;
            this.txtBlkHeader.Text = subsectionModel.Label;
            this.txtBlkHeader.ToolTip = SubsectionExpandedCtrl.GenerateToolTip(subsectionModel.Label, subsectionModel.Description);
            this.MouseDown += new System.Windows.Input.MouseButtonEventHandler(this.OnMouseDown);
            this.txtBlkHeader.FontWeight = FontWeights.UltraLight;
            this.txtBlkHeader.FontSize = 12;
            this.txtBlkHeader.Opacity = 0.3;
            this.Name = "subsection_" + Math.Abs(this.subsectionModel.Label.GetHashCode()).ToString(CultureInfo.InvariantCulture);
            this.Loaded += SubsectionExpandedCtrl_Loaded;
            this.Unloaded += SubsectionExpandedCtrl_UnLoaded;

            this.ProvenanceMenuClick += new EventHandler<ProvenanceModeEventArgs>(SubsectionExpandedCtrl_ProvenanceMenuClick);
        }

        void SubsectionExpandedCtrl_ProvenanceMenuClick(object sender, ProvenanceModeEventArgs e)
        {
            // Note: dummy implementation to avoid warning
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        private SubsectionExpandedCtrl()
        {
            InitializeComponent();
            this.UpdateClick += delegate(object sender, ModelEventArgs args) { };
            this.Focusable = true;
        }

        /// <summary>
        /// Gets the left flow connection element.
        /// </summary>
        public FrameworkElement LeftControlFlowConnectionElement
        {
            get { return this.rectangleLeft; }
        }

        /// <summary>
        /// Gets the right flow connection element.
        /// </summary>
        public FrameworkElement RightControlFlowConnectionElement
        {
            get { return this.rectangleRight; }
        }
        
        /// <summary>
        /// Select the element.
        /// </summary>
        public void OnSelectElement()
        {
            this.OnMouseDown(this, null);
        }

        /// <summary>
        /// The event handler for the loaded event.
        /// </summary>
        /// <param name="sender">source</param>
        /// <param name="e">event args.</param>
        private void SubsectionExpandedCtrl_Loaded(object sender, RoutedEventArgs e)
        {
            this.GetComposerControl();
            this.AddInteractionHandlers();
        }

        /// <summary>
        /// The event handler for the unloaded event.
        /// </summary>
        /// <param name="sender">source</param>
        /// <param name="e">event args.</param>
        private void SubsectionExpandedCtrl_UnLoaded(object sender, RoutedEventArgs e)
        {
             this.RemoveInteractionHandlers();
        }

        /// <summary>
        /// Attach event handlers according to the mode.
        /// </summary>
        private void AddInteractionHandlers()
        {
            if (this.composerControl != null && this.composerControl.CurrentRenderMode == RenderMode.ComposerMode)
            {
                this.CreateContextMenu();
            }
            else if (this.composerControl != null && this.composerControl.CurrentRenderMode == RenderMode.ProvenanceMode)
            {
                this.ContextMenu = new ContextMenu();
                this.ContextMenu.Visibility = Visibility.Hidden;
                this.ContextMenu.IsEnabled = false;
            }
        }

        /// <summary>
        /// Remove event handlers according to the mode.
        /// </summary>
        private void RemoveInteractionHandlers()
        {
            if (this.composerControl != null && this.composerControl.CurrentRenderMode == RenderMode.ComposerMode)
            {
                this.ContextMenu = null;
            }
        }

        /// <summary>
        /// Fired on click of ungroup of a subsection.
        /// </summary>
        /// <param name="sender">
        /// ContextMenu.
        /// </param>
        /// <param name="e">
        /// RoutedEventArgs.
        /// </param>
        private void OnSubsectionUngroup(object sender, RoutedEventArgs e)
        {
            if (this.RemoveSubsection != null)
            {
                this.RemoveSubsection.Invoke(sender, new RemoveSubsectionEventArgs(this.subsectionModel));
            }
        }

        /// <summary>
        /// Fired on expand click.
        /// </summary>
        /// <param name="sender">
        /// Expand button.
        /// </param>
        /// <param name="e">
        /// RoutedEventArgs.
        /// </param>
        private void OnSubsectionCollapseClick(object sender, RoutedEventArgs e)
        {
            if (this.subsectionModel != null)
            {
                this.subsectionModel.IsCollapsed = true;
                if (!this.collapsedLoadInitialized)
                {
                    this.CollapsedCtrl.Loaded += new RoutedEventHandler(this.OnCollapsedCtrlLoaded);
                    this.collapsedLoadInitialized = true;
                }
            }
        }

        private void OnCollapsedCtrlLoaded(object sender, RoutedEventArgs e)
        {
            this.CollapsedCtrl.OnSelectElement();
        }

        /// <summary>
        /// Generates the Stack Panel with name and Description
        /// </summary>
        /// <param name="name">string</param>
        /// <param name="description">string</param>
        /// <returns></returns>
        private static TextBlock GenerateToolTip(string paramName, string paramDescription)
        {
            TextBlock toolTipTxtBlk = new TextBlock();
            toolTipTxtBlk.TextWrapping = TextWrapping.Wrap;
            toolTipTxtBlk.MaxWidth = 500;
            Run nameLabelBlockRun = new Run(UIDesignerCodeConstants.ACTIVITYTOOLTIPNAME);
            nameLabelBlockRun.FontWeight = FontWeights.Bold;
            Run nameTextBlockRun = new Run(paramName);
            Run newLineRun = new Run(Environment.NewLine);
            Run descriptionLabelBlockRun = new Run(UIDesignerCodeConstants.ACTIVITYTOOLTIPDESCRIPTION);
            descriptionLabelBlockRun.FontWeight = FontWeights.Bold;
            Run descriptionTextBlockRun = new Run(paramDescription);

            toolTipTxtBlk.Inlines.Add(nameLabelBlockRun);
            toolTipTxtBlk.Inlines.Add(nameTextBlockRun);
            toolTipTxtBlk.Inlines.Add(newLineRun);
            toolTipTxtBlk.Inlines.Add(descriptionLabelBlockRun);
            toolTipTxtBlk.Inlines.Add(descriptionTextBlockRun);

            return toolTipTxtBlk;
        }

        /// <summary>
        /// Fired on mouse down of the subsection control.
        /// </summary>
        /// <param name="sender">
        /// This.
        /// </param>
        /// <param name="e">
        /// MouseButtonEventArgs.
        /// </param>
        private void OnMouseDown(object sender, MouseButtonEventArgs e)
        {
            if (this.composerControl != null)
            {
                ITridentUIElement selectedElement = sender as ITridentUIElement;
                if (null == e || e.ChangedButton == MouseButton.Left)
                {
                    this.OnSelectElement(selectedElement);
                }
                else if (e.ChangedButton == MouseButton.Right && !this.composerControl.CurrentSelectedElements.Contains(selectedElement))
                {
                    this.composerControl.CurrentSelectedElements.RemoveAllItems();
                    this.OnSelectElement(selectedElement);
                }

                if (e != null)
                {
                    // Handles the logic of double click.
                    if (2 == e.ClickCount && this.composerControl.CurrentRenderMode == RenderMode.ComposerMode)
                    {
                        this.ShowActivityProperties();
                    }

                    e.Handled = true;
                }
            }
        }

        /// <summary>
        /// Show the property pane.
        /// </summary>
        protected void ShowActivityProperties()
        {
            if (this.ShowProperties != null)
            {
                this.ShowProperties.Invoke(this, new EventArgs());
            }
        }
        
        private void OnSelectElement(ITridentUIElement selectedElement)
        {
            if (!(Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)))
            {
                this.composerControl.CurrentSelectedElements.RemoveAllItems();
            }

            if (null != selectedElement)
            {
                this.composerControl.CurrentSelectedElements.SelectChildrenUpTo(selectedElement);
                Keyboard.Focus(selectedElement as UIElement);
                this.Focus();
            }
        }

        /// <summary>
        /// Get the composer control instance.
        /// </summary>
        private void GetComposerControl()
        {
            string composerCtrlName = (string)this.GetValue(ComposerControl.ComposerControlNameProperty);
            this.composerControl = Application.Current.MainWindow.FindName(composerCtrlName) as ComposerControl;
        }

        /// <summary>
        /// The position where the binding connection has to start at.
        /// </summary>
        /// <param name="relativeTo">The element relative to which the Position is to be determined.</param>
        /// <returns>The connection point.</returns>
        public Point GetBindingStartPoint(UIElement relativeTo)
        {
            if (relativeTo == null)
            {
                throw new ArgumentNullException("relativeTo");
            }

            return this.TranslatePoint(new Point(this.ActualWidth - 2.0, ((this.ActualHeight / 2.0) + 20)), relativeTo);
        }

        /// <summary>
        /// The position where the binding connection has to end at.
        /// </summary>
        /// <param name="relativeTo">The element relative to which the Position is to be determined.</param>
        /// <returns>The connection point.</returns>
        public Point GetBindingEndpoint(UIElement relativeTo)
        {
            if (relativeTo == null)
            {
                throw new ArgumentNullException("relativeTo");
            }

            return this.TranslatePoint(new Point(2, ((this.ActualHeight / 2.0) + 20)), relativeTo);
        }

        public ObservableCollection<ITridentUIElement> ChildElements
        {
            get
            {
                return this.childElements;
            }
        }

        public void AddChildElement(ITridentUIElement childElement)
        {
            #region Parameters check.

            if (null == childElement)
            {
                throw new ArgumentNullException("childElement");
            }

            #endregion Parameters check.

            this.childElements.Add(childElement);
            UIElement uiElem = childElement as UIElement;
            if (uiElem != null)
            {
                this.dckSubsectionContentHolder.Children.Add(uiElem);
            }
        }

        public void InsertChildElement(ITridentUIElement childElement, int indexToInsertAt)
        {
            #region Parameters check.

            if (null == childElement)
            {
                throw new ArgumentNullException("childElement");
            }

            if (0 > indexToInsertAt)
            {
                throw new ArgumentOutOfRangeException("indexToInsertAt");
            }

            #endregion Parameters check.

            this.childElements.Insert(indexToInsertAt, childElement);
            UIElement uiElem = childElement as UIElement;
            if (uiElem != null)
            {
                this.dckSubsectionContentHolder.Children.Insert(indexToInsertAt + this.MetaUiElementsOffset, uiElem);
            }            
        } 

        public bool RemoveChildElement(ITridentUIElement childElement)
        {
            #region Parameters check.

            if (null == childElement)
            {
                throw new ArgumentNullException("childElement");
            }

            #endregion Parameters check.

            bool opStatus = false;
            if (this.childElements.Contains(childElement))
            {
                opStatus = this.childElements.Remove(childElement);
            }

            if (opStatus)
            {
                UIElement uiElem = childElement as UIElement;
                if (uiElem != null && this.dckSubsectionContentHolder.Children.Contains(uiElem))
                {
                    this.dckSubsectionContentHolder.Children.Remove(uiElem);
                }
            }

            return opStatus;
        }

        public void RemoveChildElementAt(int indexOfChild)
        {
            #region Parameters check.

            if (0 > indexOfChild || (this.childElements.Count - 1) < indexOfChild)
            {
                throw new ArgumentOutOfRangeException("indexOfChild");
            }

            if (0 > indexOfChild || (this.dckSubsectionContentHolder.Children.Count - 1) < indexOfChild)
            {
                throw new ArgumentOutOfRangeException("indexOfChild");
            }

            #endregion Parameters check.

            this.childElements.RemoveAt(indexOfChild);
            this.dckSubsectionContentHolder.Children.RemoveAt(indexOfChild + this.MetaUiElementsOffset);
        }

        /// <summary>
        /// Get the main content holder.
        /// </summary>
        public Panel MainContentHolder
        {
            get { return this.dckSubsectionContentHolder; }
        }

        public event EventHandler DeleteElement;

        public event EventHandler SelectElement;

        public event EventHandler ShowProperties;

        public event EventHandler InsertIntoSubsection;

        public event EventHandler<ProvenanceModeEventArgs> ProvenanceMenuClick;

        public void ApplySelectedStyle()
        {
            // Apply the background of the border.
            this.MainBorder.BorderBrush = Brushes.Orange;
        }

        public void ApplyUnselectedStyle()
        {
            // Revert back all the changes made while selecting.
            this.MainBorder.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FFBDBDBD"));
        }

        public int FetchUIElementPosition()
        {
            int index = -1;
            if (this.Model.ParentModel != null && this.composerControl != null && this.composerControl.UIElements.ContainsKey(this.Model.ParentModel.UniqueId))
            {
                ICompositeUIElement parentElem = this.composerControl.UIElements[this.Model.ParentModel.UniqueId] as ICompositeUIElement;
                if (parentElem != null && parentElem.ChildElements.Contains(this))
                {
                    index = parentElem.ChildElements.IndexOf(this);
                }
            }

            return index;
        }

        public BaseModel Model
        {
            get { return this.subsectionModel; }
        }

        #region ITridentUIElement Members

        public void CreateContextMenu()
        {
            this.ContextMenu = new ContextMenu();

            MenuItem deleteMenu = new MenuItem();
            deleteMenu.Header = TridentResourceManager.GetString("DeleteActivityLabel");
            deleteMenu.Icon = TridentUIActivityElement.GetIcon(@"pack://Application:,,,/Microsoft.Research.ScientificWorkflow.TridentUtilities;component/Images/ScientificWorkflow/ActivitesDelete.png");
            deleteMenu.Name = "deleteSubsection";
            deleteMenu.Click += new RoutedEventHandler(this.OnDelete);
            this.ContextMenu.Items.Add(deleteMenu);

            if (SubsectionMetadata.Data != null)
            {
                MenuItem insertIntoSubsectionMenu = new MenuItem();
                insertIntoSubsectionMenu.Header = TridentResourceManager.GetString("CreateSubsection");
                insertIntoSubsectionMenu.Name = "createSubsection";
                insertIntoSubsectionMenu.Icon = TridentUIActivityElement.GetIcon(@"pack://Application:,,,/Microsoft.Research.ScientificWorkflow.TridentUtilities;component/Images/ScientificWorkflow/EmptySubSection.png");
                insertIntoSubsectionMenu.Click += new RoutedEventHandler(this.OnInsertIntoSubsectionClick);
                this.ContextMenu.Items.Add(insertIntoSubsectionMenu);
            }

            MenuItem propertyPaneMenu = new MenuItem();
            propertyPaneMenu.Header = TridentResourceManager.GetString("PropertiesContextMenuLabel");
            propertyPaneMenu.Icon = TridentUIActivityElement.GetIcon(@"pack://Application:,,,/Microsoft.Research.ScientificWorkflow.TridentUtilities;component/Images/ScientificWorkflow/ActivitiesProperties.png");
            propertyPaneMenu.Name = "propertyPaneMenu";
            propertyPaneMenu.Click += new RoutedEventHandler(this.OnPropertiesClicked);
            this.ContextMenu.Items.Add(propertyPaneMenu);

            MenuItem ungroupItem = new MenuItem();
            ungroupItem.Header = "Remove Subsection";
            ungroupItem.Name = "removeSubsection";
            ungroupItem.Icon = TridentUIActivityElement.GetIcon(@"pack://Application:,,,/Microsoft.Research.ScientificWorkflow.TridentUtilities;component/Images/ScientificWorkflow/removeSubsection.png");
            ungroupItem.ToolTip = "Removes only the subsection, " + Environment.NewLine + "keeping the contents of the subsection intact.";
            ungroupItem.Click += new RoutedEventHandler(this.OnSubsectionUngroup);
            this.ContextMenu.Items.Add(ungroupItem);
            this.subsectionSpecificItems.Add(ungroupItem);

            this.ContextMenu.Opened += new RoutedEventHandler(this.OnContextMenuOpened);
        }

        private void OnContextMenuOpened(object sender, RoutedEventArgs e)
        {
            if (this.composerControl.CurrentSelectedElements.OnlySubsections)
            {
                foreach (MenuItem item in this.subsectionSpecificItems)
                {
                    item.Visibility = Visibility.Visible;
                }
            }
            else
            {
                foreach (MenuItem item in this.subsectionSpecificItems)
                {
                    item.Visibility = Visibility.Collapsed;
                }
            }
        }

        public event EventHandler<ModelEventArgs> UpdateClick;

        #endregion

        public void OnDeleteClick()
        {
            if (this.DeleteElement != null)
            {
                this.DeleteElement.Invoke(this, new EventArgs());
            }
        }

        public void OnSelectClick()
        {
            if (this.SelectElement != null)
            {
                this.SelectElement.Invoke(this, new EventArgs());
            }
        }
        
        private void OnDelete(object sender, RoutedEventArgs e)
        {
            this.OnDeleteClick();
        }

        private void OnPropertiesClicked(object sender, RoutedEventArgs e)
        {
            if (this.ShowProperties != null)
            {
                this.ShowProperties.Invoke(this, new EventArgs());
            }
        }

        private void OnInsertIntoSubsectionClick(object sender, RoutedEventArgs e)
        {
            if (this.InsertIntoSubsection != null)
            {
                this.InsertIntoSubsection.Invoke(this, new EventArgs());
            }
        }
    }
}
